package com.gjy.spring.aspect;

import com.gjy.spring.entity.AuditRecord;
import com.gjy.spring.record.AuditRecorder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * @author gjy
 * @version 1.0
 * @since 2025-03-03 19:24:47
 */
@Aspect
@Component
public class AuditAspect implements BeanFactoryAware {
    private final AuditRecorder auditRecorder;
    private BeanFactory beanFactory;
    private final ExpressionParser parser = new SpelExpressionParser();
    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    public AuditAspect(AuditRecorder auditRecorder) {
        this.auditRecorder = auditRecorder;
    }

    @Around("@annotation(auditAnnotation)")
    public Object auditOperation(ProceedingJoinPoint joinPoint, OperationAudit auditAnnotation) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();
        EvaluationContext context = createEvaluationContext(method, args);

        AuditRecord record = prepareAuditRecord(auditAnnotation, context);
        record.setPreState(evaluateExpression(auditAnnotation.preState(), context));

        try {
            Object result = joinPoint.proceed();
            context.setVariable("result", result);
            record.setPostState(evaluateExpression(auditAnnotation.postState(), context));
            record.setSuccess(true);
            return result;
        } catch (Exception ex) {
            record.setSuccess(false);
            record.setErrorMessage(ex.getMessage());
            throw ex;
        } finally {
            auditRecorder.record(record);
        }
    }

    private EvaluationContext createEvaluationContext(Method method, Object[] args) {
        MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(null, method, args, parameterNameDiscoverer);
        context.setBeanResolver(new BeanFactoryResolver(this.beanFactory));
        return context;
    }

    private AuditRecord prepareAuditRecord(OperationAudit annotation, EvaluationContext context) {
        AuditRecord record = new AuditRecord();
        record.setOperationCode(annotation.operationCode());
        record.setDescription(evaluateExpression(annotation.description(), context));
        record.setOperator(evaluateExpression(annotation.operator(), context));
        record.setOperationTime(LocalDateTime.now());
        return record;
    }

    private String evaluateExpression(String expr, EvaluationContext context) {
        try {
            return parser.parseExpression(expr).getValue(context, String.class);
        } catch (Exception e) {
            return "";
        }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
